/**
 * Die Klasse ist die Root-Klasse aller Container-Klassen.
 * Ihre Aufgabe besteht darin eine Reihe von Widgets zu verwalten,
 * die Eingabe an Sie weitezuleiten etc
 */

package jcurses.widgets;

import java.util.Hashtable;
import java.util.Vector;

import jcurses.system.Toolkit;
import jcurses.util.Rectangle;

/**
 * This class is a superclass for widget containers, that is, for widgets, that
 * can contain other widgets
 */
public abstract class WidgetContainer extends Widget {

	private Vector<Widget> _widgets = new Vector<Widget>();
	private Hashtable<Widget, Object> _constraints = new Hashtable<Widget, Object>();

	private long lastPaint;

	/**
	 * LayoutManager
	 */

	private LayoutManager _layoutManager = null;

	/**
	 * @return container's layout manager
	 */
	public LayoutManager getLayoutManager() {
		if (_layoutManager == null) {
			setLayoutManager(new DefaultLayoutManager());
		}
		return _layoutManager;
	}

	/**
	 * The method sets container's layout manager
	 * 
	 * @param layoutManager
	 *            new layout manager
	 */
	public void setLayoutManager(LayoutManager layoutManager) {
		if (_layoutManager != null) {
			_layoutManager.unbindFromContainer();
		}
		_layoutManager = layoutManager;
		_layoutManager.bindToContainer(this);
	}

	private Rectangle getChildsClippingRectangle() {
		Rectangle rect = (getChildsRectangle() == null) ? getSize()
				: getChildsRectangle();
		int x = getAbsoluteX() + rect.getX();
		int y = getAbsoluteY() + rect.getY();
		rect.setLocation(x, y);
		return rect;
	}

	@SuppressWarnings("unused")
	private Rectangle getClippingRect(Rectangle rect, Widget widget) {

		Rectangle widgetRectangle = new Rectangle(widget.getAbsoluteX(),
				widget.getAbsoluteY(), widget.getSize().getWidth(), widget
						.getSize().getHeight());
		Rectangle clip = rect.intersection(widgetRectangle);
		return clip;

	}

	private void packChild(Widget widget, Object constraint) {
		getLayoutManager().layout(widget, constraint);
	}

	/**
	 * The method adds a widget to the container, declaring widget's lyouting
	 * constraints. This method is called by layout manager and cann't be called
	 * by developer. To add a widget to the container, a developer must use
	 * methods of container's layout manager.
	 * 
	 * @param widget
	 *            widget to add
	 * @constraint layouting constraints
	 */
	protected void addWidget(Widget widget, Object constraint) {
		_widgets.add(widget);
		_constraints.put(widget, constraint);
		widget.setParent(this);
	}

	@Override
	protected void doPaint() {
		paintSelf();

		Toolkit.setClipRectangle(getChildsClippingRectangle());
		for (int i = 0; i < _widgets.size(); i++) {
			Widget widget = (Widget) _widgets.elementAt(i);
			widget.paint();
		}
		Toolkit.unsetClipRectangle();
		lastPaint = System.currentTimeMillis();
	}

	@Override
	protected void doRepaint() {
		if (System.currentTimeMillis() - lastPaint > 66) { // Control frame rate
			repaintSelf();
			Toolkit.setClipRectangle(getChildsClippingRectangle());
			for (int i = 0; i < _widgets.size(); i++) {
				Widget widget = (Widget) _widgets.elementAt(i);
				widget.repaint();
			}
			Toolkit.unsetClipRectangle();
			lastPaint = System.currentTimeMillis();
		}
	}

	/**
	 * This method returns the rectangle, that is used as painting surface for
	 * container's children If null is returned, the entire container's surface
	 * is used.
	 */
	protected Rectangle getChildsRectangle() {
		return null;
	}

	/**
	 * The method returns a list of input widgets within the container.
	 * 
	 * @return input widgets within container
	 */

	protected Vector<IFocusable> getListOfFocusables() {
		Vector<IFocusable> result = new Vector<IFocusable>();
		for (int i = 0; i < _widgets.size(); i++) {
			Widget widget = _widgets.elementAt(i);
			if (widget instanceof IFocusable) {
				IFocusable fwidget = (IFocusable) widget;
				if (fwidget.isFocusable() && widget.getVisible()) {
					result.add(fwidget);
				}
			} else if (widget instanceof WidgetContainer) {
				result.addAll(((WidgetContainer) widget).getListOfFocusables());
			}
		}

		return result;

	}

	/**
	 * The method returns a list of widgets, that can handle shortcuts, within
	 * the container.
	 * 
	 * @return widgets within container, that can handle shortcuts
	 */

	protected Vector<Widget> getListOfWidgetsWithShortCuts() {
		Vector<Widget> result = new Vector<Widget>();
		for (int i = 0; i < _widgets.size(); i++) {
			Widget widget = _widgets.elementAt(i);
			if (widget instanceof IShortCutable) {
				if (((IShortCutable) widget).getShortCut() != null) {
					result.add(widget);
				}
			} else if (widget instanceof WidgetContainer) {
				result.addAll(((WidgetContainer) widget)
						.getListOfWidgetsWithShortCuts());
			}
		}

		return result;

	}

	/**
	 * The method layouts all childrens bei the widget, using containers layout
	 * manager. The method is called bei framework, before it paints a window-
	 */
	protected void pack() {
		for (int i = 0; i < _widgets.size(); i++) {
			Widget widget = (Widget) _widgets.elementAt(i);
			packChild(widget, _constraints.get(widget));
			if (widget instanceof WidgetContainer) {
				((WidgetContainer) widget).pack();
			}
		}
	}

	/**
	 * The method paits the container self, that is all except childrens. Is
	 * called by <code>doPaint</code>. Must be overrided by derived classes.
	 */
	protected abstract void paintSelf();

	/**
	 * The method removes a widget from the container. This method is called by
	 * layout manager and cann't be called by developer. To remove a widget from
	 * the container, a developer must use methods of container's layout
	 * manager.
	 * 
	 * @param widget
	 *            widget to remove
	 * 
	 */

	protected void removeWidget(Widget widget) {
		_widgets.remove(widget);
		_constraints.remove(widget);
		widget.setParent(null);
	}

	/**
	 * The method repaints the container self, that is all, except childrens. Is
	 * called by <code>doRepaint</code>. Must be overrided by derived classes.
	 */
	protected abstract void repaintSelf();

}
